#ifndef MODEL_H
#define MODEL_H

#include <vector>
#include <string>
#include <iostream>
#include <cmath>
#include <fstream>
#include <map> 

#include "utility.h"
#include "mesh.h"
#include "operator.h"
#include "collide.h"

#include "texture.h"


enum VIEW_MODE {FILL_MODE, FRAME_MODE, POINT_MODE} ;
enum SHADE_MODE{SMOOTH, FLAT} ;

class Model
{
public:
	Model():selectMode (true), cullingState (true), partialUpdate(false), paintUpdate( false),uvexpand(NULL), m_activeLayer(0),blenderLayer(false),
		pCullResult(NULL), depth(NULL), s_x(0), s_y(0), s_w(WINDOW_WIDTH), s_h(WINDOW_HEIGHT), 
		mouseRGBBuffer(NULL), displayBuffer(NULL), pBrush (NULL), shadeMode(FLAT), viewMode (FILL_MODE){} 

	Model (const char * fileName) : selectMode (true), cullingState (true), partialUpdate(false), paintUpdate( false),uvexpand(NULL), m_activeLayer(0),blenderLayer(false),
		pCullResult(NULL), mp_texture(NULL),depth(NULL), s_x(0), s_y(0), s_w(WINDOW_WIDTH), s_h(WINDOW_HEIGHT), 
		mouseRGBBuffer(NULL), displayBuffer(NULL), pBrush (NULL), shadeMode(FLAT), viewMode (FILL_MODE)
	{
		loadModel (fileName);
		//brush.color = int_RGB (0, 0, 0);
	}
	~Model() 
	{
		//Remove the culling Result ;
		if (pCullResult) delete pCullResult ;
		delete collide;
		if (mouseRGBBuffer) delete mouseRGBBuffer ;
		if (displayBuffer) delete displayBuffer ;
		if (mp_texture) delete mp_texture; 
		if (pBrush)
		{
			delete pBrush ;
			pBrush = NULL; 
		}
	}
public:
	int loadModel (const char * fileName)
	{
		mesh.finalize();
		std::ifstream ifs (fileName);
		ifs >> mesh; 
		ostr.open("out.log") ;
		ostr<< mesh.v_faces.size()<<std::endl;
		/*Random face color*/
		srand(10);

		collide = new Collide(& this->mesh);

		
		pBrush = new SolidBrush ;
		//pBrush->load_pattern("D:\\workspace\\data\\brush\\siglogo.ppm");
		//pBrush->set_transparent_color(int_RGB(255,255,255));

		// create texture 
		mp_texture = new Texture (texture_height, texture_width); 
		if (mp_texture == NULL) throw ("Fail to allocate the memory for texture");

		FACE_CONTAINER::iterator f_iter ;
		for (f_iter = mesh.faces.begin(); f_iter != mesh.faces.end(); ++f_iter)
		{
			(f_iter->second)->set_texture ( mp_texture );
		}
		return 0;
	}
	Texture * get_texture () { return this->mp_texture; }
	int saveModel (const char * fileName)
	{
		return 0; 
	}
	void setShadeMode (SHADE_MODE mode = SMOOTH)
	{
		this->shadeMode = mode ;
	}
	void initLight(void)
	{
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		glEnable(GL_NORMALIZE);

		// Light model parameters:
		// -------------------------------------------

		GLfloat lmKa[] = {0.0, 0.0, 0.0, 0.0 };
		glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmKa);

		glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, 1.0);
		glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0.0);

		// -------------------------------------------
		// Spotlight Attenuation

		GLfloat spot_direction[] = {1.0, -1.0, -1.0 };
		GLint spot_exponent = 30;
		GLint spot_cutoff = 180;

		glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
		glLighti(GL_LIGHT0, GL_SPOT_EXPONENT, spot_exponent);
		glLighti(GL_LIGHT0, GL_SPOT_CUTOFF, spot_cutoff);

		GLfloat Kc = 1.0;
		GLfloat Kl = 0.0;
		GLfloat Kq = 0.0;

		glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION,Kc);
		glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, Kl);
		glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, Kq);


		// ------------------------------------------- 
		// Lighting parameters:

		GLfloat light_pos[] = {0.0f, 5.0f, 5.0f, 1.0f};
		
		GLfloat light_Ka[]  = {0.8f, .8f, .8f, 1.0f};
		GLfloat light_Kd[]  = {1.0f, 1.0f, 1.0f, 1.0f};
		GLfloat light_Ks[]  = {.8f, .8f, .8f, 1.0f};

		//GLfloat light_Ka[]  = {1.0f, 0.5f, 0.5f, 1.0f};
		//GLfloat light_Kd[]  = {1.f, 0.1f, 0.1f, 1.0f};
		//GLfloat light_Ks[]  = {1.0f, 1.0f, 1.0f, 1.0f};

		glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light_Ka);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_Kd);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_Ks);

		// -------------------------------------------
		// Material parameters:

		//GLfloat material_Ka[] = {0.5f, 0.0f, 0.0f, 1.0f};
		//GLfloat material_Kd[] = {0.4f, 0.4f, 0.5f, 1.0f};
		//GLfloat material_Ks[] = {0.8f, 0.8f, 0.0f, 1.0f};
		GLfloat material_Ka[] = {.5f, .5f, .5f, 1.0f};
		GLfloat material_Kd[] = {0.4f, 0.4f, 0.4f, 1.0f};
		GLfloat material_Ks[] = {0.8f, 0.8f, 0.8f, 1.0f};
		GLfloat material_Ke[] = {0.1f, 0.1f, 0.1f, 0.0f};
		GLfloat material_Se = 20.0f;

		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_Ka);
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_Kd);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_Ks);
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_Ke);
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material_Se);

	}
	void setViewMode (VIEW_MODE vm = FILL_MODE)
	{
		this->viewMode = vm ;
		switch (viewMode)
		{
		case FILL_MODE:
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			glEnable(GL_DEPTH_TEST);
			glEnable(GL_CULL_FACE);
			break;
		case FRAME_MODE:
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			//glDisable(GL_DEPTH_TEST);
			glDisable(GL_CULL_FACE);
			break;
			//glEnable(GL_DEPTH_TEST);
			//glEnable(GL_CULL_FACE);
		case POINT_MODE:
			glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
			//glDisable(GL_DEPTH_TEST);
			glDisable(GL_CULL_FACE);
			break;
		}
	}

	void init()
	{
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		//glViewport(0, 0, 500, 600);
		glShadeModel(GL_SMOOTH);
		glClearColor(0, 0, 0, 1);   
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_CULL_FACE);
		glEnable(GL_BLEND);
		glEnable(GL_SCISSOR_TEST);
		// track material ambient and diffuse from surface color, call it before glEnable(GL_COLOR_MATERIAL)

		
		//glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
		//glEnable(GL_COLOR_MATERIAL);

		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);   
		//glClearColor(1.0f, 1.0f, 1.0f, 1.0f);   
		glClearStencil(0);                              // clear stencil buffer
		glClearDepth(1.0f);                             // 0 is near, 1 is far
		glDepthFunc(GL_LEQUAL);

		initLight();
		setViewMode(/*FRAME_MODE*/);

		mp_texture->init();
		FACE_CONTAINER::iterator f_iter ;
		for (f_iter = mesh.faces.begin(); f_iter != mesh.faces.end(); ++f_iter)
		{
			//(f_iter->second)->set_texture ( mp_texture );
			f_iter->second->write_tex_param(); 
		}

	}
	void brush_paint()
	{

		//map_brush.set_color(brush.color);
		//uvexpand ;
		pBrush->paint(uvexpand);
	}

	void make_frustum(double fovY, double aspectRatio, double front, double back)
	{
		const double DEG2RAD = 3.14159265 / 180;

		double tangent = tan(fovY/2 * DEG2RAD);   // tangent of half fovY
		double height = front * tangent;          // half height of near plane
		double width = height * aspectRatio;      // half width of near plane

		// params: left, right, bottom, top, near, far
		glFrustum(-width, width, -height, height, front, back);
	}

	//Picking begin ;
	Face * pick_face ()
	{
		Point3 p1, p2;
		pBrush->pick_ray(p1, p2);
		Face * face = collide->segmentCollide(p1, p2);
		if (face)
		{
			face->selected = !(face->selected) ;
		}
		return face ;
	}
	void pick_edge ()
	{
		Point3 p_start, p_end;
		pBrush->pick_ray(p_start, p_end);
		Face * face = collide->segmentCollide(p_start, p_end);


		if (face == NULL) return ;
		Point3 vcoord[3];
		Point3 win_coord[3];
		face->Face2VertexCoord(vcoord);
		for (int i = 0; i < 3; ++i ) 
		{
			win_coord [i] = pBrush->objVM.mappingObj2Win(vcoord[i]);
			win_coord [i].z = 0.0f;
		}
		Point2 pos = pBrush->get_win_pos() ; 
		Point3 mouse_pos(pos.x, pos.y, 0.0f);

		short tag = -1; 
		float distance = INFINITY ;

		for (int i = 0; i < 3; ++i)
		{
			Vector3 p0 = win_coord[i] - win_coord[(i+1)%3];
			p0 = p0.normalize(); 

			Vector3 p1 = mouse_pos - win_coord[(i+1)%3] ;

			Vector3 dis_v = p0^p1; 
			if (dis_v.length() < distance )
			{
				tag = i ;
				distance = dis_v.length();
			}
		}
		// Update halfedge to this face
		face->strongEdge[tag] = ! face->strongEdge[tag]; 
		// Update halfedge to the opposite face
		HalfEdge * he = face->halfEdge(tag); 
		he = he->flip ;
		he->face->strongEdge[he->face->halfEdgeCount(he)] = face->strongEdge[tag];

	}
	//Picking end


	void bfs_face_culling()
	{
		if (mesh.faces.size() == 0) return ;
		if (cullingState == true)
		{
			if (pCullResult) delete pCullResult ;
			pCullResult = NULL ;
			OPBFSCulling culling(&mesh, &(*pBrush), collide);
			//OPCulling culling(&mesh, mappingView.modelViewMatrix, mappingView.projectionMatrix, mappingView.viewPort);
			pCullResult = culling;

			if(pCullResult->v_faces.size() > 0)
			{
				//expand
				OPLSCM lscm (pCullResult);
				uvexpand = lscm;
				cullingState = false;
				
				Face * centerFace = culling.getCenterTriangle();
				
				//Compute the barycentric coords
				Point3 v_o[3];
				Point3 v_w[3];
				centerFace->Face2VertexCoord(v_o);
				v_w[0] = pBrush->mappingObj2Win(v_o[0]);
				v_w[1] = pBrush->mappingObj2Win(v_o[1]);
				v_w[2] = pBrush->mappingObj2Win(v_o[2]);
				Point3 v_b (pBrush->get_win_pos().x, pBrush->get_win_pos().y, 1);

				float matrix[3][3], invmatrix[3][3];
				matrix[0][0] = v_w[0].x ; matrix[0][1] = v_w[1].x ; matrix[0][2] = v_w[2].x ;
				matrix[1][0] = v_w[0].y ; matrix[1][1] = v_w[1].y ; matrix[1][2] = v_w[2].y ;
				matrix[2][0] = 1.0f		; matrix[2][1] = 1.0f	  ; matrix[2][2] = 1.0f		;


				inverse3(matrix, invmatrix);
				Vector3 barycoord = multiply_vector(invmatrix, v_b);

				//Affine transformation
				Point2 v[3];
				centerFace->Face2TexCoord(v);
				//Point2 center = (v[0]+v[1]+v[2]) * float(1.0/3.0);
				Point2 center = v[0] * barycoord.x +v[1] * barycoord.y +v[2] * barycoord.z ;
				Point2 trans;
				float scale ;
				float rotate_cos, rotate_sin ;
				compute_affine(centerFace, (*pBrush), center, trans, scale, rotate_cos, rotate_sin);
				OPAffineTransformation afftrans(uvexpand, trans, scale, rotate_cos, rotate_sin) ; 
				uvexpand = afftrans ;
				
			}
			else
			{
				uvexpand = pCullResult ;
			}
		}
	}
	void draw_expand_result()
	{
		//this->setUpdated(cullingView, true, 40, 40, WINDOW_WIDTH - 40 * 2, WINDOW_HEIGHT - 80);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

		if (this->mappingView.viewMode == VIEW) return ;
		//Not every time we need to redraw all the region.
		//For example when we move the brush. 
		//

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(60, float(500)/600, cullingView.projectionNear, cullingView.projectionFar);

		glMatrixMode(GL_MODELVIEW);

		glPushMatrix();
		glLoadIdentity();
		//View transform
		glRotatef(mappingView.cameraAglY, 0, 0, 1); // Rotate along Z-axis
		glTranslatef(0, 0, mappingView.cameraDis );

		glTranslatef(-mappingView.cameraX, 0.0f, 0.0f);
		glTranslatef(0.0f, mappingView.cameraY, 0.0f);		
		
		mappingView.viewMatrix.getCoordEnv();

		//Draw the grid 
		glPushMatrix();
		glRotatef(-90, 1, 0, 0);
		drawGrid(10, 1);
		glPopMatrix();

		pBrush->texVM.getCoordEnv();
		
		//bfs_face_culling() ;
		if (uvexpand)
		{
			for (unsigned i = 0; i < uvexpand->v_faces.size(); ++i)
			{
				this->draw_tex_faces(uvexpand->v_faces[i]);
			}
		}
		
		glPopMatrix();

		//Draw Brush here
		
		if (mappingView.viewMode == EDIT)
		{
			glPushMatrix();
			glLoadIdentity();
			glTranslatef(0, 0, -0.01);
			pBrush->tex_draw();
			glPopMatrix();
		}
	}

	void read_pixel (int x, int y, unsigned nWidth, unsigned nHeight)
	{
		if (mouseRGBBuffer)
		{
			delete []mouseRGBBuffer;
			mouseRGBBuffer = NULL;
		}
		if (displayBuffer)
		{
			delete []displayBuffer;
			displayBuffer = NULL;
		}
		if (depth)
		{
			delete []depth;
			depth = NULL ;
		}
		mouseRGBBuffer = new unsigned char [4 * (nWidth) * (nHeight ) + 4];
		glReadBuffer( GL_FRONT );
		glReadPixels( x ,  y, nWidth, nHeight, GL_RGBA, GL_UNSIGNED_BYTE, mouseRGBBuffer);
		displayBuffer = new unsigned char [4 * (nWidth) * (nHeight ) + 4];
		memcpy(displayBuffer, mouseRGBBuffer, sizeof(unsigned char) * (4 * (nWidth) * (nHeight ) + 4));
		depth = new float [4 * (nWidth) * (nHeight ) + 4];
		for (unsigned i = 0; i < 4 * (nWidth) * (nHeight ) + 4; ++i)
		{
			depth [i] = 200.0f;//2.99; //z-buffer point into the screen
		}

	}
	void draw_pixel (int x, int y, unsigned nWidth, unsigned nHeight)
	{
		//glRasterPos2d(x, y);
		//glDrawBuffer(GL_BACK);
		unsigned bx = pBrush->get_win_pos().x;
		unsigned by = WINDOW_HEIGHT - pBrush->get_win_pos().y;
		static unsigned oldx = bx ;
		static unsigned oldy =  by ;
		//unsigned oldx = brush.oldPos.x ;
		//unsigned oldy = WINDOW_HEIGHT - brush.oldPos.y ;

		for (unsigned i = oldx - 5; i <= oldx + 5; ++i)
		{
			for (unsigned j = oldy-5; j <= oldy + 5; ++j)
			{
				mouseRGBBuffer [4 * (j * (nWidth ) + i)] =  displayBuffer [4 * (j * (nWidth ) + i)];
				mouseRGBBuffer [4 * (j * (nWidth ) + i)+1] = displayBuffer [4 * (j * (nWidth ) + i) + 1];
				mouseRGBBuffer [4 * (j * (nWidth ) + i)+2] = displayBuffer [4 * (j * (nWidth ) + i) + 2];
				mouseRGBBuffer [4 * (j * (nWidth ) + i)+3] = displayBuffer [4 * (j * (nWidth ) + i) + 3];
			}
		}

		for (unsigned i = bx - 5; i <= bx + 5; ++i)
		{
			for (unsigned j = by-5; j <= by + 5; ++j)
			{
				mouseRGBBuffer [4 * (j * (nWidth ) + i)] = unsigned char(255);
				mouseRGBBuffer [4 * (j * (nWidth ) + i)+1] = unsigned char(0);;
				mouseRGBBuffer [4 * (j * (nWidth ) + i)+2] = unsigned char(0);;
				mouseRGBBuffer [4 * (j * (nWidth ) + i)+3] = unsigned char(255);;
			}
		}
		oldx = bx ;
		oldy = by ;

		//glDrawPixels(nWidth, nHeight, GL_RGBA, GL_UNSIGNED_BYTE, displayBuffer);
		glDrawPixels(nWidth,nHeight,GL_DEPTH_COMPONENT,GL_FLOAT,depth);
		glDrawPixels(nWidth, nHeight, GL_RGBA, GL_UNSIGNED_BYTE, mouseRGBBuffer);
		//delete displayBuffer ;
	}
	void set_perspective_viewport()
	{
		if (! this->partialUpdate )
		{
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		}
		// Set up viewing matrices
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(60, float(500)/600, cullingView.projectionNear, cullingView.projectionFar);
		
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	}
	void draw_perspective () 
	{

		set_perspective_viewport();

		glPushMatrix();
		//1. Adjust Camera
		glTranslatef(0, 0, cullingView.cameraDis);
		//glRotatef(-90, 0, 0,1); // heading
		glRotatef(cullingView.cameraAglX , 1, 0, 0); // pitch
		glRotatef(cullingView.cameraAglY , 0, 1, 0); // heading
		//2. Get Coord Env
		cullingView.viewMatrix.getCoordEnv();
		pBrush->getObjCoordEnv();
		
		
		bfs_face_culling() ;
		if (partialUpdate != true)
		{

			//draw code goes here: draw the visible faces
			OPCulling culling(&mesh, & cullingView.viewMatrix); 
			PseudoMesh * cullingResult = culling ; 
			unsigned size = cullingResult->v_faces.size();
			for (unsigned i = 0; i < size; ++i)
			{
				draw_faces(cullingResult->v_faces[i]);
			}
	

			//Draw Strong Edge
			for (unsigned i = 0;i < size; ++i)
			{
				drawStrongEdge(cullingResult->v_faces[i]); 
			}
			delete cullingResult ;
			/*	
			if (!mouseRGBBuffer)
			{
			read_pixel(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); 
			}
			else
			{
			draw_pixel(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); 
			}

			partialUpdate = false;
			*/

			//}
		}
		else
		{
			partialUpdate = false ;
		}
		drawGrid(10, 1);
		glPopMatrix();

		if (cullingView.viewMode == EDIT)
		{
			//draw_pixel(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

			glPushMatrix();
			glLoadIdentity();
			glTranslatef(0, 0, -0.01);
			pBrush->draw();	//Pixel radius 130
			glPopMatrix(); 
		}
	}
	


	void upSampling ()
	{		
		for (unsigned idx = 0; idx < uvexpand->v_faces.size(); ++ idx)
		{
			if (true == uvexpand->v_faces[idx]->selected)
			{
				Face * face = uvexpand->v_faces[idx] ;
				face->upSampling();
				/*
				Face * face = uvexpand->v_faces[idx] ;
				std::vector<int_RGB> old_samples (face->samples);
				unsigned res = face->resolution ;
				res *= 2;
				face->samples.resize((res+1) * (res+2)/2);
				face->resolution = res;

				for (unsigned i = 0; i <=res; ++i)
				{
					for (unsigned j = 0; j <= res - i ; ++j)
					{
						int k = res - i - j;
						if (i % 2 == 0 && j % 2 == 0 && k % 2 == 0)
						{
							face->samples[barycentric2index(res, i,j)] =
								old_samples[barycentric2index(res/2, i/2,j/2)] ;
							continue;
 						}
						//Fix i, two neighbors are (i,j-1,k+1) and (i, j+1,k-1)
						if (i%2 == 0)
						{
							face->samples[barycentric2index(res, i, j)] =
								old_samples[barycentric2index(res/2, i/2,(j-1)/2)] * 0.5f 
								+ old_samples[barycentric2index(res/2, i/2,(j+1)/2)] * 0.5f;
							continue;
						}
						//Fix j, two neighbors are (i-1,j,k+1) and (i+1, j,k-1)
						if (j%2 == 0)
						{
							face->samples[barycentric2index(res, i, j)] =
								old_samples[barycentric2index(res/2, (i-1)/2,(j)/2)] * 0.5f 
								+ old_samples[barycentric2index(res/2, (i+1)/2,(j)/2)] * 0.5f;
							continue;
						}
						//Fix k, two neighbors are (i-1,j+1,k) and (i+1, j-1,k)
						if (k%2 == 0)
						{
							face->samples[barycentric2index(res, i, j)] =
								old_samples[barycentric2index(res/2, (i+1)/2,(j-1)/2)] * 0.5f 
								+ old_samples[barycentric2index(res/2, (i-1)/2,(j+1)/2)] * 0.5f;
							continue;
						}
					}
				}
				*/
			}	
		}
	}
	void downSampling()
	{
		for (unsigned i = 0; i < uvexpand->v_faces.size(); ++i)
		{
			if (true == uvexpand->v_faces[i]->selected)
			{
				Face * face = uvexpand->v_faces[i] ;
				face->downSampling();
				/*
				unsigned res = face->resolution ;
				res /= 2;
				for (unsigned i = 0 ; i <= res; ++i)
				{
					for (unsigned j = 0; j <= res - i; ++j)
					{
						face->samples[barycentric2index(res, i,j)] = 
							face->samples [barycentric2index (res * 2, i *2, j * 2)] ;
					}
				}
				face->samples.resize ((res + 1) * (res + 2)/2);
				face->resolution = res;
				*/
			}
		}
	}
	// The following functions are interactive function
	bool getUpdated (ViewParameter & vp) const {return vp.updated ;}
	void setUpdated (ViewParameter & vp, bool status)
	{
		setUpdated (vp, status, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
	}
	void setUpdated (ViewParameter & vp, bool status, int x, int y, int w, int h ) 
	{
		vp.updated = status;
		s_x = x ;
		s_y = y;
		s_w = w ; 
		s_h = h ;
		//glViewport(x, y, w, h);
	}

	void setMousePos(ViewParameter & vp, int x, int y){vp.mouseX = x; vp.mouseY = y;}			//MK_LEFTDOWN
	void setMouseLeft (ViewParameter & vp, bool flag) {vp.mouseLeftDown = true;}
	bool getMouseLeft (ViewParameter & vp) const {return vp.mouseLeftDown ;}
	void setMouseRight (ViewParameter & vp, bool flag) {vp.mouseRightDown = true;}
	bool getMouseRight (ViewParameter & vp) const {return vp.mouseRightDown ;}
	void setWindowSize (ViewParameter & vp, int w, int h ) 
	{
		vp.windowSizeChanged = true;
		vp.windowWidth = w; 
		vp.windowHeight = h;
	}

	void setShader (MeshColorShader * s) {this->shader = s ;}


	// These functions are used to control the camera .
	void setCamera(float posX, float posY, float posZ, float targetX, float targetY, float targetZ)
	{
		//glMatrixMode(GL_MODELVIEW);
		//glLoadIdentity();
		//gluLookAt(posX, posY, posZ, targetX, targetY, targetZ, 0, 1, 0); // eye(x,y,z), focal(x,y,z), up(x,y,z)
	}
	void rotateCamera (ViewParameter & vp, int x, int y)
	{
		vp.cameraAglY -= (vp.mouseX - x) ;
		vp.cameraAglX -= (vp.mouseY - y) ;
		vp.mouseX = x;
		vp.mouseY = y;
	}
	void zoomCamera (ViewParameter & vp, int delta)
	{
		vp.cameraDis += (delta - vp.mouseY) * 0.05f;
		vp.mouseY = delta;
	}
	void translateCamera (ViewParameter & vp, int x, int y)
	{
		/*
		double winx, winy, winz;
		gluProject(0.0f, 0.0f, 0.0f, mappingView.viewMatrix.modelViewMatrix, 
			mappingView.viewMatrix.projectionMatrix, mappingView.viewMatrix.viewPort, & winx, & winy, & winz);

		Vector3 oldPos = mappingWin2Obj(Vector3(vp.mouseX, vp.mouseY, winz), 
			mappingView.viewMatrix.modelViewMatrix, mappingView.viewMatrix.projectionMatrix, mappingView.viewMatrix.viewPort);
		Vector3 newPos = mappingWin2Obj(Vector3(x, y, winz), 
			mappingView.viewMatrix.modelViewMatrix, mappingView.viewMatrix.projectionMatrix, mappingView.viewMatrix.viewPort);
		vp.cameraX -= newPos.x - oldPos.x ;
		vp.cameraY += newPos.y - oldPos.y ;
		vp.mouseX = x;
		vp.mouseY = y;
		*/
	}

	void moveBrush(ViewParameter & vp, int x, int y)
	{
		pBrush->set_win_pos(Vector2(x,y));
	}
	void setActiveLayer (short idx_layer)
	{
		this->m_activeLayer = idx_layer ;

		for (unsigned i = 0; i < mesh.v_faces.size(); ++i)
		{
			mesh.v_faces[i]->setActiveLayer(idx_layer); 
			mesh.v_faces[i]->blendLayers(); 
		}

	}
	void setBlenderFlag (bool f = true)
	{
		for (unsigned i = 0; i < mesh.v_faces.size(); ++i)
		{
			mesh.v_faces[i]->setBlenderFlag(f ); 
		}
	}
protected:

	void drawGrid(float size, float step)
	{
		// disable lighting
		glDisable(GL_LIGHTING);
		glBegin(GL_LINES);
		glColor3f(0.3f, 0.3f, 0.3f);
		float offset = 0.0f;
		for(float i=step; i <= size; i+= step)
		{
			glVertex3f(-size, offset,  i);   // lines parallel to X-axis
			glVertex3f( size, offset,  i);
			glVertex3f(-size, offset, -i);   // lines parallel to X-axis
			glVertex3f( size, offset, -i);

			glVertex3f( i, offset, -size);   // lines parallel to Z-axis
			glVertex3f( i, offset,  size);
			glVertex3f(-i, offset, -size);   // lines parallel to Z-axis
			glVertex3f(-i, offset,  size);
		}

		// x-axis
		glColor3f(0.5f, offset, 0);
		glVertex3f(-size, offset, 0);
		glVertex3f( size, offset, 0);

		// z-axis
		glColor3f(0,offset,0.5f);
		glVertex3f(0, offset, -size);
		glVertex3f(0, offset,  size);

		glEnd();
		//glLineWidth(1);
		// enable lighting back
		glEnable(GL_LIGHTING);

		//drawCylinder();

		glFlush();
	}
	void drawStrongEdge (Face * face)
	{
						//draw strong edges ;
		Vector3 p[3];
		face->Face2VertexCoord(p); 
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_BLEND);
		glLineWidth(5);
		glDisable(GL_LIGHTING);
		glBegin(GL_LINES);
		glColor4f(0.0f,0.0f,0.0f,0.75);
		for (int i = 0; i < 3; ++i)
		{
			if (face->strongEdge[i])
			{
				glVertex3f(p[i].x, p[i].y, p[i].z);
				glVertex3f(p[(i+1)%3].x, p[(i+1)%3].y, p[(i+1)%3].z);
			}
		}
		glEnd();
		glDisable(GL_BLEND);
		glEnable(GL_LIGHTING);
		glLineWidth(1);
		glFlush();
	}
protected:

	void draw_tex_faces (Face * face)
	{
		Point2 p[3];
		face->Face2TexCoord(p);
		Point3 point[3];
		point[0].x = p[0].x ; point[0].z = 0.0f ; point[0].y = p[0].y;
		point[1].x = p[1].x ; point[1].z = 0.0f ; point[1].y = p[1].y;
		point[2].x = p[2].x ; point[2].z = 0.0f ; point[2].y = p[2].y;

		float tk, th, tc; 
		face->compute_tex_param(point, tk, th, tc);

		unsigned res = face->resolution ;
		unsigned num_samples = (res + 1) * (res + 2) / 2;
		GLfloat  * color1  = new GLfloat[num_samples * 4] ;
		for (unsigned i = 0; i < num_samples; ++i)
		{
			color1 [4*i + 0] = face->samples[i].r / 255.0f;
			color1 [4*i + 1] = face->samples[i].g / 255.0f;
			color1 [4*i + 2] = face->samples[i].b / 255.0f;
			color1 [4*i + 3] = 1.0f;
		}
		draw_triangles(point, color1, res, tk, th, tc, Vector3(0,0,1));
		if (face->selected)
		{
			draw_blend_triangles (point,Vector3(0,0,1));
		}
		delete []color1 ;
	}

	void draw_faces (Face * face)
	{
		face->blendLayers();
		unsigned res = face->resolution ;
		unsigned num_samples = (res + 1) * (res + 2) / 2;
		GLfloat  * color1  = new GLfloat[num_samples * 4] ;
		for (unsigned i = 0; i < num_samples; ++i)
		{
				color1 [4*i + 0] = face->samples[i].r / 255.0f;
				color1 [4*i + 1] = face->samples[i].g / 255.0f;
				color1 [4*i + 2] = face->samples[i].b / 255.0f;
				color1 [4*i + 3] = 1.0f;
		}
		Point p[3];
		face->Face2VertexCoord(p);

		Vertex * vp[3];
		face->Face2Vertex(vp);
		Vector3 normals[3];
		normals[0] = vp[0]->normal(); normals[1] = vp[1]->normal(); normals[2] = vp[2]->normal(); 
		if (this->shadeMode == FLAT)
		{
			draw_triangles(p, color1, res, face->mk, face->mh, face->mc, face->normal());
		}
		else if (this->shadeMode == SMOOTH)
		{
			draw_triangles(p, normals, color1, res, face->mk, face->mh, face->mc);
		}
		if (face->selected)
		{
			draw_blend_triangles (p, face->normal());
		}
		delete []color1 ;


	}
	

	void draw_blend_triangles (Point point[3], const Vector3 & normal )
	{
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_BLEND);
		glBegin(GL_TRIANGLES);
		glColor4f(0.75,0.75,0.75,0.75);
		glNormal3f(normal.x, normal.y, normal.z); 
		glVertex3f(point[0].x, point[0].y, point[0].z);
		glVertex3f(point[1].x, point[1].y, point[1].z);
		glVertex3f(point[2].x, point[2].y, point[2].z);
		glEnd();
		glDisable(GL_BLEND);
	}

	//smooth shade mode 
	void draw_triangles (Point point[3], Vector3 normal[3], GLfloat * color , int res, float k, float h, float c )
	{
		//shader->get_tex_coord(point, k, h, c);
		shader->enable_draw_face();
		shader->set_triangle_shader_uniform(point, color, res, k, h, c);

		glBegin(GL_TRIANGLES);
		glNormal3f(normal[0].x, normal[0].y, normal[0].z); 
		glTexCoord2f(0.0, 0.0); glVertex3f(point[0].x, point[0].y, point[0].z);
		glNormal3f(normal[1].x, normal[1].y, normal[1].z); 
		glTexCoord2f(c, 0.0); glVertex3f(point[1].x, point[1].y, point[1].z);
		glNormal3f(normal[2].x, normal[2].y, normal[2].z); 
		glTexCoord2f(k, h); glVertex3f(point[2].x, point[2].y, point[2].z);
		glEnd();
		glFlush();

		shader->disable_draw_face();
	}

	//Flat shade mode
	void draw_triangles (Point point[3], GLfloat * color , int res, float k, float h, float c, const Vector3 & normal)
	{
		//shader->get_tex_coord(point, k, h, c);
		shader->enable_draw_face();
		shader->set_triangle_shader_uniform(point, color, res, k, h, c);

		glBegin(GL_TRIANGLES);
		glNormal3f(normal.x, normal.y, normal.z); 
		glTexCoord2f(0.0, 0.0); glVertex3f(point[0].x, point[0].y, point[0].z);
		glTexCoord2f(c, 0.0); glVertex3f(point[1].x, point[1].y, point[1].z);
		glTexCoord2f(k, h); glVertex3f(point[2].x, point[2].y, point[2].z);

		glEnd();
		glFlush();

		shader->disable_draw_face();
	}



public:
	ViewParameter cullingView;
	ViewParameter mappingView; 
	bool cullingState ;
	bool partialUpdate ;
	bool paintUpdate ;

private:
	Mesh mesh ;
	unsigned face_size ;
	STATES states;	
	std::ofstream ostr ;		// This is used to write log file. 


	Vector3 frustumVertices[8];         // 8 vertices of frustum
	Vector3 frustumNormals[6];          // 6 face normals of frustum
	//GLUquadricObj* quadricId;

	
	MeshColorShader * shader ;
	PseudoMesh * uvexpand ;
	PseudoMesh * pCullResult ;

public:
	//SolidBrush brush ;
	//GaussianBrush brush ;
	//PatternBrush brush; 
	Brush * pBrush ;
	// support for layer
	short m_activeLayer;
	bool blenderLayer ; 

	bool selectMode ;
private:
	unsigned s_x, s_y, s_w, s_h ;

	Collide  * collide ;

	unsigned char * displayBuffer;
	unsigned char * mouseRGBBuffer ;
	float * depth ;


	//Set display style
	SHADE_MODE shadeMode;
	VIEW_MODE viewMode; 


	// for new texture
	 Texture * mp_texture; 
        
     static const unsigned texture_height = 4096; 
     static const unsigned texture_width = 2248; 


};
#endif